﻿using System;
using System.Collections.Generic;
using System.Drawing;

namespace NNImage
{
    public class KohonenMap
    {
        #region Pola prywatne
        private int _rozmiarWyjscia;
        private int _rozmiarWejscia;
        private int _ileRamek;
        #endregion

        #region Pola publiczne
        public Neuron[,] warstwaWyjsciowa;
        public List<List<byte>> wektoryWejsciowe;
        public List<List<byte>> wektoryWejscioweRandom;
        #endregion

        #region Konstruktor
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="neurony">Ilość neuronów w sieci</param>
        /// <param name="rozmiarWejscia">Rozmiar wektora wejściowego (ramki)</param>
        /// <param name="funkcjaOdleglosci">Zastosowana miara odległości</param>
        /// <param name="funkcjaSasiedztwa">Zastosowana miara sąsiedztwa między neuronami</param>
        /// <param name="alfa">Współczynnik uczenia alfa</param>
        /// <param name="potencjal">Minimalny potencjał neuronu potrzebny do wyścigu</param>
        /// <param name="sigma">Promień sąsiedztwa sigma</param>
        public KohonenMap(int neurony, int rozmiarWejscia, IFunkcjaOdleglosci funkcjaOdleglosci, IFunkcjaSasiedztwa funkcjaSasiedztwa, double alfa, double potencjal, double sigma)
        {
            if (neurony < 1 || funkcjaOdleglosci == null || funkcjaSasiedztwa == null)
                throw (new Exception("Błędne parametry sieci"));
            _rozmiarWejscia = rozmiarWejscia;
            _rozmiarWyjscia = neurony;
            warstwaWyjsciowa = new Neuron[neurony, neurony];
            Random r = new Random();
            for (int i = 0; i < neurony; ++i)
                for (int j = 0; j < neurony; ++j)
                {
                    warstwaWyjsciowa[i, j] = new Neuron(i, j, rozmiarWejscia, funkcjaOdleglosci, funkcjaSasiedztwa, potencjal, alfa, sigma);
                    warstwaWyjsciowa[i, j].LosujWagi(0, 255, r);
                }

        }
        #endregion

        #region Właściwości
        public int RozmiarWejscia
        {
            get
            {
                return _rozmiarWejscia;
            }
        }

        public int RozmiarWyjscia
        {
            get
            {
                return _rozmiarWyjscia;
            }
        }

        public int IleRamek
        {
            get
            {
                return _ileRamek;
            }
        }
        #endregion

        #region Metody
        /// <summary>
        /// Metoda szukająca zwycięskiego neuronu
        /// </summary>
        /// <param name="wektorWejsciowy">Wejściowy wektor odniesienia</param>
        /// <returns>Obiekt klasy Neuron reprezntujący zwycięzcę</returns>
        public Neuron ZnajdzZwyciezce(List<byte> wektorWejsciowy)
        {
            Neuron zwyciezca = warstwaWyjsciowa[0, 0];
            double min = zwyciezca.Oblicz(wektorWejsciowy);
            double temp;
            for (int i = 0; i < _rozmiarWyjscia; ++i)
                for (int j = 0; j < _rozmiarWyjscia; ++j)
                {
                    temp = warstwaWyjsciowa[i, j].Oblicz(wektorWejsciowy);
                    if (temp < min && !warstwaWyjsciowa[i, j].CzyZwycieski)
                    {
                        min = temp;
                        zwyciezca = warstwaWyjsciowa[i, j];
                    }

                }
            return zwyciezca;
        }

        /// <summary>
        /// Metoda przetwarzająca wejściową bitmapę na zestaw gotowych do użycia wektorów jednowymiarowych.
        /// Wektory są następnie losowo tasowane.
        /// </summary>
        /// <param name="bitmapa">Obraz wejściowy</param>
        public void WczytajWejscia(Bitmap bitmapa)
        {
            int rozmiarWejscia = Convert.ToInt32(Math.Sqrt(_rozmiarWejscia));
            if (bitmapa.Width % rozmiarWejscia != 0 || bitmapa.Height % rozmiarWejscia != 0)
                throw new Exception("Nieprawidłowy rozmiar ramki lub obrazka!");

            int ramkiPoziomo = bitmapa.Width / rozmiarWejscia;
            int ramkiPionowo = bitmapa.Height / rozmiarWejscia;
            _ileRamek = ramkiPionowo * ramkiPoziomo;
            byte kolor;
            List<List<byte>> temp = new List<List<byte>>(_ileRamek);
            List<byte> wektor=new List<byte>(_rozmiarWejscia);
            Random r = new Random();

            for (int i = 0; i < ramkiPionowo; ++i)
                for (int j = 0; j < ramkiPoziomo; ++j)
                {
                    temp.Add(new List<byte>(rozmiarWejscia * rozmiarWejscia));
                    for (int k = 0; k < rozmiarWejscia; ++k)
                        for (int l = 0; l < rozmiarWejscia; ++l)
                        {
                            kolor = Convert.ToByte((bitmapa.GetPixel(l + j * rozmiarWejscia, k + i * rozmiarWejscia).R + bitmapa.GetPixel(l + j * rozmiarWejscia, k + i * rozmiarWejscia).G + bitmapa.GetPixel(l + j * rozmiarWejscia, k + i * rozmiarWejscia).B) / 3);
                            temp[i * ramkiPoziomo + j].Add(kolor);
                        }
                }
            wektoryWejsciowe = new List<List<byte>>(temp);
            wektoryWejscioweRandom = new List<List<byte>>(_ileRamek);
            for (int i = 0; i < ramkiPionowo * ramkiPoziomo; ++i)
            {
                wektor = temp[r.Next(ramkiPionowo * ramkiPoziomo - i)];
                wektoryWejscioweRandom.Add(wektor);
                temp.Remove(wektor);
            }


        }

        /// <summary>
        /// Metoda korygująca wagi i potencjał wszystkich neuronów.
        /// </summary>
        /// <param name="wektor">Aktualnie rozpatrywana ramka</param>
        /// <param name="iteracja">Aktualna iteracja</param>
        public void KorygujWagi(List<byte> wektor, int iteracja)
        {
            Point zwyciezca = ZnajdzZwyciezce(wektor).Koordynaty;
            warstwaWyjsciowa[zwyciezca.X, zwyciezca.Y].CzyZwycieski = true;
            for (int i = 0; i < _rozmiarWyjscia; ++i)
                for (int j = 0; j < _rozmiarWyjscia; ++j)
                {
                    warstwaWyjsciowa[i, j].KorygujWagi(wektor, zwyciezca, iteracja);
                    warstwaWyjsciowa[i, j].KorygujPotencjal(_rozmiarWyjscia * _rozmiarWyjscia);
                }
        }
        #endregion

    }
}
